//===============================================================================
// XGenPlus Tool
// ==============================================================================
// Please visit http://www.codeplex.com/xgenplus for latest updates.
//
// This source is subject to the GNU General Public License v2.
// 
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;
using System.CodeDom;
using Microsoft.CSharp;
using System.Collections;
using System.CodeDom.Compiler;
using System.Xml;
using System.Xml.Serialization;
using System.Runtime.Remoting;

namespace XGenPlus.Generator
{
    public sealed class Runner : MarshalByRefObject
    {
        Logger xgenLog = null;
        Parameters param = null;

        public Runner(Logger l, Parameters p)
        {
            xgenLog = l;
            param = p;
        }

        /// <summary>
        /// Generate serializers for the given assembly and compile the same
        /// </summary>
        /// <returns></returns>
        public bool GenerateAndCompile()
        {


            string assemblyName = param.AssemblyName;
            List<string> excludeList = param.ExcludeList;
            List<string> includeList = param.IncludeList;
            List<string> referenceList = param.ReferenceList;
            bool nocompile = param.NoCompile;
            bool nogenerate = param.NoGenerate;
            string copyto = param.CopyTo;
            bool serializeAll = param.SerializeAll;
            string startFrom = param.From;

            //Add reference to XGenPlus.Serializerlib
            referenceList.Add("XGenPlus.SerializerLib.dll");

            Assembly asm = Assembly.LoadFrom(assemblyName);

            if (asm == null || asm.GetTypes().Length == 0)
            {

                xgenLog.Write("Unable to load assembly or no types in assembly");
                return false;
            }


            string targetPath = string.Empty;
            string assemblyNameWithOutExt = string.Empty;

            if (null == startFrom || startFrom == "")
                assemblyNameWithOutExt = Path.GetFileName(Path.GetFileNameWithoutExtension(asm.Location));
            else
            {
                assemblyNameWithOutExt = startFrom;
                string startFromNamespace = startFrom + ".";


                List<string> newList = new List<string>();

                //If any namespaces are there in the include list that doesn't match with the given namespace, discard it

                for (int i = 0; i < includeList.Count; i++)
                {
                    if (includeList[i].ToLower().StartsWith(startFromNamespace.ToLower()))
                        newList.Add(includeList[i]);
                    else
                        xgenLog.Write("Warning: Ignored include parameter - " + includeList[i]);

                }

                includeList = newList;

                if (includeList.Count == 0)
                    //Make sure that only serializers for types in this namespace is generated
                    includeList.Add(startFromNamespace);

            }

            try
            {
                targetPath = Path.Combine(Path.GetDirectoryName(asm.Location), assemblyNameWithOutExt + ".Serializers");

                if (Directory.Exists(targetPath) && nogenerate == false)
                {
                    string[] files = Directory.GetFiles(targetPath, "*.cs");
                    for (int i = 0; i < files.Length; i++)
                    {
                        try
                        {
                            File.Delete(files[i]);
                        }
                        catch { }
                    }

                }
                else
                    Directory.CreateDirectory(targetPath);
            }
            catch (Exception e)
            {
                xgenLog.Write("Unable to create folder");
                return false;
            }

            //Try to load other assemblies

            CompilerParameters parameters = new CompilerParameters();

            for (int i = 0; i < referenceList.Count; i++)
            {

                parameters.ReferencedAssemblies.Add(referenceList[i]);
                LoadAssembly(referenceList[i]);

            }

            parameters.ReferencedAssemblies.Add("System.dll");
            parameters.ReferencedAssemblies.Add("System.Data.dll");
            parameters.ReferencedAssemblies.Add("System.Xml.dll");
            parameters.ReferencedAssemblies.Add(assemblyName);


            parameters.GenerateInMemory = false;
            parameters.GenerateExecutable = false;
            parameters.IncludeDebugInformation = false;
            parameters.OutputAssembly = Path.Combine(Path.GetDirectoryName(asm.Location), assemblyNameWithOutExt + ".Serializers.dll");

            if (null != copyto && copyto != "")
            {
                try
                {
                    if (!copyto.ToLower().EndsWith(".dll") && !copyto.ToLower().EndsWith(".exe"))
                    {
                        if (null == startFrom || startFrom == "")
                            copyto = Path.Combine(copyto, assemblyNameWithOutExt + ".Serializers.dll");
                        else
                            copyto = Path.Combine(copyto, startFrom + ".Serializers.dll");
                    }
                }
                catch { }
            }

            try
            {
                List<string> listExclude = GetList(excludeList);
                List<string> listInclude = GetList(includeList);

                if (!nogenerate)
                {
                    XmlSerializerGenerator gen = new XmlSerializerGenerator(xgenLog);
                    gen.GenerateCode(asm.GetTypes(), targetPath, listExclude, listInclude, assemblyNameWithOutExt, serializeAll);
                    xgenLog.Write("Code generation done");
                }
            }
            catch (Exception e)
            {
                xgenLog.Write("Invoking code generator failed " + e.Message);
                return false;
            }


            try
            {
                if (!nocompile)
                    InvokeCompiler(targetPath, parameters, copyto);
            }
            catch (Exception e)
            {
                xgenLog.Write("Invoking compiler failed. " + e.Message);
            }



            return true;
        }

        /// <summary>
        /// Invokes the compiler
        /// </summary>
        /// <param name="targetPath"></param>
        /// <param name="parameters"></param>
        private void InvokeCompiler(string targetPath, CompilerParameters parameters, string copyto)
        {
            string[] codeFiles = Directory.GetFiles(targetPath, "*.cs");


            ICodeCompiler compiler =
                            new CSharpCodeProvider().CreateCompiler();
            CompilerResults results =
                compiler.CompileAssemblyFromFileBatch(
                parameters, codeFiles);

            xgenLog.Write("Invoking Compiler..");
            if (results.Errors.HasErrors)
            {
                xgenLog.Write("Error while compilation");

                foreach (CompilerError c in results.Errors)
                    if (!c.IsWarning)
                        xgenLog.Write(c.FileName + "(" + c.Line + "," + c.Column + ") : " + "error " + c.ErrorNumber + ": " + c.ErrorText);


            }
            else
            {
                xgenLog.Write("Compilation Success");
                if (null != copyto && copyto != "")
                {
                    try
                    {
                        File.Copy(parameters.OutputAssembly, copyto, true);
                        xgenLog.Write("Compiled assembly copied from " + parameters.OutputAssembly + " to " + copyto);
                    }
                    catch (Exception ex)
                    {
                        xgenLog.Write("Unable to copy compiled assembly from " + parameters.OutputAssembly + " to " + copyto);
                    }
                }
            }
        }


        /// <summary>
        /// Get list of items to an array
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        private List<string> GetList(List<string> array)
        {
            if (array == null) return new List<string>();
            else return array;
        }


        /// <summary>
        /// Try to load an assembly to the application domain
        /// </summary>
        /// <param name="asm"></param>
        private void LoadAssembly(string asm)
        {
            try
            {
                Assembly.LoadFrom(asm);
            }
            catch (Exception e1)
            {
                try
                {
                    Assembly.LoadFile(asm);
                }
                catch (Exception e2)
                {
                }
            }
        }

        /// <summary>
        /// Invokes the runner in a different application domain. Required to keep the configuration
        /// </summary>
        /// <param name="l"></param>
        /// <param name="p"></param>
        /// <returns></returns>
        internal static bool InvokeRunnerInOwnAppDomain(XGenPlus.Logger l, Parameters p)
        {
            //Create a temporary configuration file
            string configFile = Path.GetTempFileName() + ".config";

            using (StreamWriter sw = new StreamWriter(configFile))
            {
                // Create a serialization file
                sw.Write(configInfo);

            }

            AppDomainSetup appSetup = new AppDomainSetup();
            appSetup.ApplicationName = typeof(Program).Namespace;
            appSetup.ApplicationBase = Path.GetDirectoryName(typeof(Runner).Assembly.Location);
            appSetup.ConfigurationFile = configFile;

            AppDomain domain = AppDomain.CreateDomain(appSetup.ApplicationName, null, appSetup);
            try
            {
                // Create the runner

                ObjectHandle runnerHandle = domain.CreateInstance(
                    Assembly.GetExecutingAssembly().FullName,
                    "XGenPlus.Generator.Runner", false, 0, null,
                    new object[] { 
						l,
						p
						},
                    null, null, null);


                //Invoke the run method
                XGenPlus.Generator.Runner run = (XGenPlus.Generator.Runner)runnerHandle.Unwrap();
                bool ret = run.GenerateAndCompile();
                return ret;
            }

            finally
            {
                AppDomain.Unload(domain);

                // clean up dll,pdb,config files now as they got unloaded  with the appdomain
                // we may be deleting other application files here ;)
                l.Write("Cleaning up temporary files....");
                foreach (string file in Directory.GetFiles(Path.GetTempPath()))
                {
                    if (file.EndsWith(".pdb", StringComparison.InvariantCultureIgnoreCase) ||
                        file.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase) ||
                        file.EndsWith(".config", StringComparison.InvariantCultureIgnoreCase))
                    {
                        try
                        {
                            File.Delete(file);
                        }
                        catch { }
                    }
                }
            }
        }

        const string configInfo =
@"<?xml version='1.0' encoding='utf-8' ?>
<configuration>
	<system.diagnostics>
		<switches>
			<add name='XmlSerialization.Compilation' value='4'/>
		</switches>
	</system.diagnostics>
</configuration>";
    }
}
